package com.opages.mvc.adapter.nacos.model;
import com.alibaba.nacos.api.NacosFactory;
import com.alibaba.nacos.api.naming.NamingService;
import com.opages.mvc.adapter.common.properties.ConfigurationProperties;
import com.opages.mvc.adapter.common.utils.ServerUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.env.*;

import javax.annotation.PostConstruct;
import javax.management.MalformedObjectNameException;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import static com.alibaba.nacos.api.PropertyKeyConst.*;

/**
 * Nacos服务发现配置类
 * @author daizhong.liu
 * @create 2020-07-09 17:30
 **/
@ConfigurationProperties("spring.mvc.nacos.discovery")
public class NacosDiscoveryProperties {
    public static final String PREFIX = "spring.mvc.nacos.discovery";
    /**
     * 服务名称
     * Service name.
     */
    private String serviceName;
    /**
     * nacos服务地址，IP:端口
     */
    private String serverAddr;
    /**
     * 访问服务器时要使用的主机名。
     * Hostname to use when accessing server.
     */
    private String hostname;

    /**
     * 端口
     * Port to register the service under (defaults to listening port).
     */
    private Integer port;

    /**
     * namespace, separation registry of different environments.
     */
    private String namespace;
    /**
     * weight for service instance, the larger the value, the larger the weight.
     */
    private float weight = 1;

    /**
     * cluster name for nacos .
     */
    private String clusterName = "DEFAULT";

    /**
     * group name for nacos.
     */
    private String group = "DEFAULT_GROUP";

    /**
     * extra metadata to register.
     */
    private Map<String, String> metadata = new HashMap<>();

    /**
     * whether your service is a https service.
     */
    private boolean secure = false;

    /**
     * access key for namespace.
     */
    private String accessKey="";

    /**
     * secret key for namespace.
     */
    private String secretKey="";
    /**
     * 心跳时间,s/秒 m/分钟 h/小时
     **/
    private String ttl="10s";
    private String endpoint = "";
    /**
     * nacos命令服务
     **/
    private static NamingService namingService;
    @Autowired
    private Environment environment;
    private static final Pattern PATTERN = Pattern.compile("-(\\w)");

    @PostConstruct
    public void init() {
        metadata.put("preserved.register.source", "SPRING_MVC");
        if (secure) {
            metadata.put("secure", "true");
        }

        namespace = Objects.toString(namespace, "");

        //未设置客户端IP地址则获取本地IP地址
        if (hostname == null || "".equals(hostname)) {
            String ipv4EndPoints = ServerUtils.getIPV4EndPoints();
            if(!"".equals(ipv4EndPoints)) {
                hostname = ipv4EndPoints;
            }
        }
        //未设置客户端端口,则获取本地容器端口
        if(this.port == null || this.port.intValue() <= 0) {
            try {
                String port = ServerUtils.getPort();
                if (!"".equals(port)) {
                    this.port = Integer.parseInt(port);
                }
            } catch (MalformedObjectNameException e) {
                e.printStackTrace();
            }
        }
    }


    public String getServiceName() {
        return serviceName;
    }

    public void setServiceName(String serviceName) {
        this.serviceName = serviceName;
    }

    public String getHostname() {
        return hostname;
    }

    public void setHostname(String hostname) {
        this.hostname = hostname;
    }

    public Integer getPort() {
        return port;
    }

    public void setPort(Integer port) {
        this.port = port;
    }

    public String getNamespace() {
        return namespace;
    }

    public void setNamespace(String namespace) {
        this.namespace = namespace;
    }

    public float getWeight() {
        return weight;
    }

    public void setWeight(float weight) {
        this.weight = weight;
    }

    public String getClusterName() {
        return clusterName;
    }

    public void setClusterName(String clusterName) {
        this.clusterName = clusterName;
    }

    public String getGroup() {
        return group;
    }

    public void setGroup(String group) {
        this.group = group;
    }

    public Map<String, String> getMetadata() {
        return metadata;
    }

    public void setMetadata(Map<String, String> metadata) {
        this.metadata = metadata;
    }

    public boolean isSecure() {
        return secure;
    }

    public void setSecure(boolean secure) {
        this.secure = secure;
    }

    public String getAccessKey() {
        return accessKey;
    }

    public void setAccessKey(String accessKey) {
        this.accessKey = accessKey;
    }

    public String getSecretKey() {
        return secretKey;
    }

    public void setSecretKey(String secretKey) {
        this.secretKey = secretKey;
    }

    public String getTtl() {
        return ttl;
    }

    public void setTtl(String ttl) {
        this.ttl = ttl;
    }

    public String getEndpoint() {
        return endpoint;
    }

    public void setEndpoint(String endpoint) {
        this.endpoint = endpoint;
    }

    public NamingService namingServiceInstance() {
        if (null != namingService) {
            return namingService;
        }

        try {
            namingService = NacosFactory.createNamingService(getNacosProperties());
        }
        catch (Exception e) {
            throw new RuntimeException("create naming service error!",e);
        }
        return namingService;
    }

    private Properties getNacosProperties() {
        Properties properties = new Properties();
        properties.put(SERVER_ADDR, serverAddr);
        properties.put(NAMESPACE, namespace);
//        properties.put(UtilAndComs.NACOS_NAMING_LOG_NAME, logName);


        if (endpoint.contains(":")) {
            int index = endpoint.indexOf(":");
            properties.put(ENDPOINT, endpoint.substring(0, index));
            properties.put(ENDPOINT_PORT, endpoint.substring(index + 1));
        }else {
            properties.put(ENDPOINT, endpoint);
        }

        properties.put(ACCESS_KEY, accessKey);
        properties.put(SECRET_KEY, secretKey);
        properties.put(CLUSTER_NAME, clusterName);
        properties.put(NAMING_LOAD_CACHE_AT_START, "false");

        enrichNacosDiscoveryProperties(properties);
        return properties;
    }
    private void enrichNacosDiscoveryProperties(Properties nacosDiscoveryProperties) {
        Map<String, Object> properties = getSubProperties((ConfigurableEnvironment) environment, PREFIX);
        properties.forEach((k, v) -> nacosDiscoveryProperties.putIfAbsent(resolveKey(k),String.valueOf(v)));
    }
    private String resolveKey(String key) {
        Matcher matcher = PATTERN.matcher(key);
        StringBuffer sb = new StringBuffer();
        while (matcher.find()) {
            matcher.appendReplacement(sb, matcher.group(1).toUpperCase());
        }
        matcher.appendTail(sb);
        return sb.toString();
    }

    private Map<String, Object> getSubProperties(ConfigurableEnvironment environment, String prefix) {
        return getSubProperties(environment.getPropertySources(), environment, prefix);
    }

    public Map<String, Object> getSubProperties(PropertySources propertySources, PropertyResolver propertyResolver, String prefix) {
        Map<String, Object> subProperties = new LinkedHashMap();
        String normalizedPrefix = normalizePrefix(prefix);
        Iterator iterator = propertySources.iterator();
        while(true) {
            PropertySource source;
            do {
                if (!iterator.hasNext()) {
                    return Collections.unmodifiableMap(subProperties);
                }
                source = (PropertySource)iterator.next();
            } while(!(source instanceof EnumerablePropertySource));

            String[] propertyNames = ((EnumerablePropertySource)source).getPropertyNames();
            int nameLength = propertyNames.length;

            for(int i = 0; i < nameLength; ++i) {
                String name = propertyNames[i];
                if (!subProperties.containsKey(name) && name.startsWith(normalizedPrefix)) {
                    String subName = name.substring(normalizedPrefix.length());
                    if (!subProperties.containsKey(subName)) {
                        Object value = source.getProperty(name);
                        if (value instanceof String) {
                            value = propertyResolver.resolvePlaceholders((String)value);
                        }
                        subProperties.put(subName, value);
                    }
                }
            }
        }
    }
    private String normalizePrefix(String prefix) {
        return prefix.endsWith(".") ? prefix : prefix + ".";
    }

    public String getServerAddr() {
        return serverAddr;
    }

    public void setServerAddr(String serverAddr) {
        this.serverAddr = serverAddr;
    }

    public Environment getEnvironment() {
        return environment;
    }

}
